package com.hczt.springcloud.oauthservice.config;

import com.hczt.springcloud.oauthservice.service.OAuthUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * 精诚所至，金石为开。
 * 石の上にも三年;陽気の発する所金石亦透る。
 * Faith moves mountains.
 *
 * @author marvin.ma
 * @create 2017-12-11 22:34
 * @desc OAuth认证授权服务端
 * 参考网站：http://www.leftso.com/blog/139.html
 * AuthorizationServerConfigurer 需要配置三个配置-重写几个方法：
 * ClientDetailsServiceConfigurer：用于配置客户详情服务，指定存储位置
 * AuthorizationServerSecurityConfigurer：定义安全约束
 * AuthorizationServerEndpointsConfigurer：定义认证和token服务
 **/
@Configuration
@EnableAuthorizationServer
public class OAuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {

    private static final String ENV_OAUTH = "authentication.oauth";

    private RelaxedPropertyResolver propertyResolver;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private OAuthUserDetailsService oAuthUserDetailsService;

    /**
     * 注入自定义token生成方式
     *
     * @return
     */
    @Bean
    public TokenEnhancer customerEnhancer() {
        return new CustomTokenEnhancer();
    }

    //@Bean
    //public TokenEnhancer tokenEnhancer() {
    //    final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    //    converter.setSigningKey("123");
    //    converter.setSigner(new MacSigner(new byte[1]));
    //    String stringKey = "dev" + "123";
    //    byte[] encodedKey = base64UrlCodec.decode(stringKey);
    //    converter.setSigner(new MacSigner(encodedKey));
    //
    //    converter.setSigningKey("123");
    //    final KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
    //    converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
    //    return converter;
    //}

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter());
    }

    /**
     * 1.配置客户端认证方式以及客户端连接参数设置
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                    .withClient("android")
                    .scopes("read", "write")     //此处的scopes是无用的，可以随意设置
                    .secret("secret")
                    .authorizedGrantTypes("password", "authorization_code", "refresh_token")
                    .accessTokenValiditySeconds(1800)    //Access token is only valid for 30 minutes.
                    .refreshTokenValiditySeconds(2880)    //Refresh token is only valid for 8*60*60 minutes---8hours.
                .and()
                    .withClient("normal-app")
                    .scopes("read", "write")
                    .authorizedGrantTypes("implicit");
    }

    /**
     * 2.配置oauth认证安全策略 从表单提交经过OAuth认证
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

    /**
     * 3.认证转换器配置使用@Bean的方式注入到当前方法中
     * 首先实现一个方法加上@Bean注解交给IOC容器管理这个Bean，
     * 在该方法中设置之前放置证书的路径以及设置的别名和密码，
     * set到Jwt认证转换器中，并返回一个转换器
     * @return
     */

    @Bean
    public TokenEnhancer accessTokenConverter() {
        final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyStoreKeyFactory keyStoreKeyFactory =
                new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));

        converter.setAccessTokenConverter(new CustomerAccessTokenConverter());
        //converter.setSigningKey("123");//对称加密的话只需要这个key就可以了
        return converter;
    }

    /**
     * 4.之后注入到认证转换器配置中调用
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        //指定认证管理器,若无，refresh_token会有OAuthUserDetailsService is required错误
        endpoints.authenticationManager(authenticationManager);
        //指定token存储位置
        endpoints.tokenStore(tokenStore());
        // 自定义token生成方式
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customerEnhancer(), accessTokenConverter()));
        endpoints.tokenEnhancer(tokenEnhancerChain);

        // 配置TokenServices参数
        DefaultTokenServices tokenServices = (DefaultTokenServices) endpoints.getDefaultAuthorizationServerTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); // 1天
        endpoints.tokenServices(tokenServices);
        super.configure(endpoints);
    }


    /**
     * Set the {@code Environment} that this component runs in.
     *
     * @param environment
     */
    @Override
    public void setEnvironment(Environment environment) {
        this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
    }
}



